﻿#region Using

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.Places;

#endregion

namespace Vk.Methods.Users
{
    /// <summary>
    ///     Пользователи
    /// </summary>
    public class Users
    {
        private readonly Api _api;

        public Users(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Возвращает расширенную информацию о пользователях.
        /// </summary>
        /// <param name="userIds">
        ///     Перечисленные через запятую ID пользователей или их короткие имена (screen_name). Максимум 1000
        ///     пользователей.
        /// </param>
        /// <param name="fields">
        ///     Перечисленные через запятую поля анкет, необходимые для получения. Доступные значения: userId,
        ///     first_name, last_name, nickname, screen_name, sex, bdate (birthdate), city, country, timezone, photo, photo_medium,
        ///     photo_big, has_mobile, rate, contacts, education, online, counters.
        /// </param>
        /// <param name="nameCase">
        ///     Падеж для склонения имени и фамилии пользователя. Возможные значения: именительный – nom,
        ///     родительный – gen, дательный – dat, винительный – acc, творительный – ins, предложный – abl. По умолчанию nom.
        /// </param>
        public async Task<List<User>> Get(IEnumerable<string> userIds = null,
                                          IEnumerable<string> fields = null,
                                          string nameCase = null)
        {
            var query = new ApiQuery("users.get", true)
                .Add("name_case", nameCase)
                .Add("fields", fields)
                .Add("user_ids", userIds);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                if (!result.ChildNodes.Any())
                {
                    return null;
                }
                return result.ChildNodes.Select(node => new User(node)).ToList();
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список идентификаторов пользователей, которые являются подписчиками пользователя. Идентификаторы
        ///     пользователей в списке отсортированы в порядке убывания времени их добавления.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя. </param>
        /// <param name="nameCase">
        ///     Падеж для склонения имени и фамилии пользователя. Возможные значения: именительный – nom,
        ///     родительный – gen, дательный – dat, винительный – acc, творительный – ins, предложный – abl. По умолчанию nom.
        /// </param>
        /// <param name="count">Количество подписчиков, информацию о которых нужно получить. </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества подписчиков. </param>
        /// <param name="fields">Список дополнительных полей, которые необходимо вернуть. </param>
        public async Task<ListCount<User>> GetFollowers(int? userId = null,
                                                        string nameCase = null,
                                                        int? count = null,
                                                        int? offset = null,
                                                        IEnumerable<string> fields = null)
        {
            var query = new ApiQuery("users.getFollowers", true)
                .Add("user_id", userId)
                .Add("name_case", nameCase)
                .Add("count", count)
                .Add("offset", offset)
                .Add("fields", fields);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                if (!result.ChildNodes.Any())
                {
                    return null;
                }

                var c = result.Int("count");
                var nodes = result.SelectNodes("items/*");
                var res = nodes.Any()
                              ? fields != null && fields.Any()
                                    ? new ListCount<User>(c.Value, nodes.Select(y => new User(y)).ToList())
                                    : new ListCount<User>(c.Value,
                                                          nodes.Select(y => new User(int.Parse(y.String())))
                                                               .ToList())
                              : null;
                return res;
            }
            return null;
        }

        /// <summary>
        ///     Индексирует текущее местоположение пользователя и возвращает список пользователей, которые находятся вблизи.
        /// </summary>
        /// <param name="latitude">
        ///     Географическая широта точки, в которой в данный момент находится пользователь, заданная в
        ///     градусах (от -90 до 90).
        /// </param>
        /// <param name="longitude">
        ///     Географическая долгота точки, в которой в данный момент находится пользователь, заданная в
        ///     градусах (от -180 до 180).
        /// </param>
        /// <param name="accuracy">Точность текущего местоположения пользователя в метрах. </param>
        /// <param name="timeout">
        ///     Время в секундах через которое пользователь должен перестать находиться через поиск по
        ///     местоположению.
        /// </param>
        /// <param name="radius">Тип радиуса зоны поиска</param>
        /// <param name="fields">Список дополнительных полей, которые необходимо вернуть. </param>
        /// <param name="nameCase">Падеж для склонения имени и фамилии пользователя. </param>
        /// <returns></returns>
        public async Task<ListCount<User>> GetNearby(float latitude,
                                                     float longitude,
                                                     int? accuracy = null,
                                                     int? timeout = null,
                                                     ApiEnum<SearchRadius> radius = null,
                                                     IEnumerable<string> fields = null,
                                                     string nameCase = null)
        {
            var query = new ApiQuery("users.getNearby")
                .Add("latitude", latitude)
                .Add("longitude", longitude)
                .Add("accuracy", accuracy)
                .Add("timeout", timeout)
                .Add("name_case", nameCase)
                .Add("fields", fields)
                .Add("radius", radius);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                if (!result.ChildNodes.Any())
                {
                    return new ListCount<User>(0, new List<User>());
                }
                return new ListCount<User>(result.Int("items/count").GetValueOrDefault(),
                                           result.ChildNodes.Select(node => new User(node)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список идентификаторов пользователей и групп, которые входят в список подписок пользователя.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя, подписки которого необходимо получить. </param>
        public async Task<UsersGroupsIDList> GetSubscriptions(int? userId = null)
        {
            var query = new ApiQuery("users.getSubscriptions", true)
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                if (!result.ChildNodes.Any())
                {
                    return null;
                }
                var groupsNodes = result.SelectNodes("groups/items/*");
                var usersNodes = result.SelectNodes("users/items/*");
                var res = new UsersGroupsIDList
                {
                    Groups =
                        new ListCount<int>(result.Int("groups/count").Value,
                                           (from ApiNode node in groupsNodes select node.Int().Value).ToList()),
                    Users =
                        new ListCount<int>(result.Int("users/count").Value,
                                           (from ApiNode node in usersNodes select node.Int().Value).ToList())
                };
                return res;
            }
            return null;
        }

        /// <summary>
        ///     Возвращает информацию о том, установил ли пользователь приложение.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя. </param>
        /// <returns>
        ///     После успешного выполнения возвращает true в случае, если пользователь установил у себя данное приложение,
        ///     иначе false.
        /// </returns>
        public async Task<bool> IsAppUser(int? userId = null)
        {
            var query = new ApiQuery("users.isAppUser")
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            return result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет пожаловаться на пользователя.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя, на которого осуществляется жалоба</param>
        /// <param name="type">Тип жалобы</param>
        /// <param name="comment">Комментарий к жалобе на пользователя</param>
        /// <returns>В случае успешной жалобы метод вернет true.</returns>
        public async Task<bool> Report(int userId, UserReportType type, string comment = null)
        {
            var query = new ApiQuery("users.report")
                .Add("user_id", userId)
                .Add("type", type.StringValue)
                .Add("comment", comment);
            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                if (!resp.ChildNodes.Any())
                {
                    return false;
                }
                return resp.Bool().Value;
            }
            return false;
        }

        /// <summary>
        ///     Возвращает список пользователей в соответствии с заданным критерием поиска.
        /// </summary>
        /// <param name="q">Cтрока поискового запроса. Например, Вася Бабич.</param>
        /// <param name="fields">
        ///     Перечисленные через запятую поля анкет, необходимые для получения. Доступные значения: userId,
        ///     first_name, last_name, nickname, screen_name, sex, bdate (birthdate), city, country, timezone, photo, photo_medium,
        ///     photo_big, has_mobile, rate, contacts, education, online.
        /// </param>
        /// <param name="count">Количество возвращаемых пользователей (максимум 1000). По умолчанию 20.</param>
        /// <param name="offset">Смещение относительно первого найденного пользователя для выборки определенного подмножества.</param>
        /// <param name="ageFrom">Начиная с какого возраста. </param>
        /// <param name="ageTo">До какого возраста.</param>
        /// <param name="birthDay">День рождения. </param>
        /// <param name="birthMonth">Месяц рождения. </param>
        /// <param name="birthYear">Год рождения. </param>
        /// <param name="city">Идентификатор города. </param>
        /// <param name="company">Название компании, в которой работают пользователи. </param>
        /// <param name="country">Идентификатор страны.</param>
        /// <param name="groupId">Идентификатор группы, среди пользователей которой необходимо проводить поиск. </param>
        /// <param name="hasPhoto">Только с фотографией</param>
        /// <param name="hometown">Название города строкой</param>
        /// <param name="interests">Интересы.</param>
        /// <param name="online">Только в сети</param>
        /// <param name="position">Название должности.</param>
        /// <param name="schoolClass">Идентификатор школьного класса.</param>
        /// <param name="religion">Религиозные взгляды.</param>
        /// <param name="school">Идентификатор школы, которую закончили пользователи.</param>
        /// <param name="schoolCity">Идентификатор города, в котором пользователи закончили школу. </param>
        /// <param name="schoolCountry">Идентификатор страны, в которой пользователи закончили школу.</param>
        /// <param name="schoolYear">Год окончания школы.</param>
        /// <param name="universityChair">Идентификатор кафедры.</param>
        /// <param name="sex">Пол</param>
        /// <param name="sort">Сортировка результатов</param>
        /// <param name="status">Семейное положение</param>
        /// <param name="university">Идентификатор ВУЗа.</param>
        /// <param name="universityCountry">Идентификатор страны, в которой пользователи закончили ВУЗ.</param>
        /// <param name="universityYear">Год окончания ВУЗа.</param>
        /// <param name="universityFaculty">Идентификатор факультета. </param>
        /// <param name="fromList">
        ///     Разделы среди которых нужно осуществить поиск, перечисленные через запятую:
        ///     friends – искать среди друзей,
        ///     subscriptions – искать среди друзей и подписок пользователя
        /// </param>
        public async Task<ListCount<User>> Search(string q = null,
                                                  IEnumerable<string> fields = null,
                                                  int? count = null,
                                                  int? offset = null,
                                                  ApiEnum<UsersSortOrder> sort = null,
                                                  int? city = null,
                                                  int? country = null,
                                                  string hometown = null,
                                                  int? universityCountry = null,
                                                  int? university = null,
                                                  int? universityYear = null,
                                                  int? universityFaculty = null,
                                                  int? universityChair = null,
                                                  ApiEnum<UserSex> sex = null,
                                                  ApiEnum<UserMaritalStatus> status = null,
                                                  int? ageFrom = null,
                                                  int? ageTo = null,
                                                  int? birthDay = null,
                                                  int? birthMonth = null,
                                                  int? birthYear = null,
                                                  bool? online = null,
                                                  bool? hasPhoto = null,
                                                  int? school = null,
                                                  int? schoolCountry = null,
                                                  int? schoolCity = null,
                                                  int? schoolYear = null,
                                                  int? schoolClass = null,
                                                  string religion = null,
                                                  string interests = null,
                                                  string company = null,
                                                  string position = null,
                                                  int? groupId = null,
                                                  IEnumerable<string> fromList = null)
        {
            var apiQuery = new ApiQuery("users.search")
                .Add("q", q)
                .Add("fields", fields)
                .Add("count", count)
                .Add("offset", offset)
                .Add("sort", sort)
                .Add("city", city)
                .Add("country", country)
                .Add("hometown", hometown)
                .Add("university_country", universityCountry)
                .Add("university", university)
                .Add("university_year", universityYear)
                .Add("university_faculty", universityFaculty)
                .Add("university_chair", universityChair)
                .Add("sex", sex)
                .Add("status", status)
                .Add("age_from", ageFrom)
                .Add("age_to", ageTo)
                .Add("birth_day", birthDay)
                .Add("birth_month", birthMonth)
                .Add("birth_year", birthYear)
                .Add("online", online)
                .Add("has_photo", hasPhoto)
                .Add("school", school)
                .Add("school_year", schoolYear)
                .Add("school_country", schoolCountry)
                .Add("school_city", schoolCity)
                .Add("school_class", schoolClass)
                .Add("religion", religion)
                .Add("interests", interests)
                .Add("company", company)
                .Add("position", position)
                .Add("group_id", groupId)
                .Add("from_list", fromList);

            var resp = await _api.Manager.Execute(apiQuery);
            if (_api.Manager.MethodSuccessed)
            {
                if (!resp.ChildNodes.Any())
                {
                    return null;
                }
                var nodes =
                    resp.SelectNodes("items/*");
                return new ListCount<User>(resp.Int("count").Value, nodes.Select(node => new User(node)).ToList());
            }
            return null;
        }
    }
}